package com.lagou.edu.factory;

import com.lagou.edu.anno.*;
import com.lagou.edu.utils.BeanFactoryUtil;
import com.mysql.jdbc.StringUtils;

import javax.servlet.ServletContext;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 工厂类 生产对象
 */
public class BeanFactory {

    private static final Map<String,Object> context = new HashMap<>();      // 存储实例化对象
    private final List<String> classPathList = new ArrayList<>();           // 存储类路径
    private final Properties properties = new Properties();                 // 存储配置文件信息
    private final Class<?> configClass;
    private final ServletContext servletContext;

    /**
     * 构造方法 初始化实例对象
     * @param contextConfig  配置类路径
     * @throws Exception     初始化异常
     */
    public BeanFactory(String contextConfig, ServletContext servletContext) throws Exception {
        try {
            this.servletContext = servletContext;
            this.configClass = BeanFactoryUtil.checkConfig(Class.forName(contextConfig));
            // 1. 获取包扫描路径下的所有类
            getClassPathList();
            // 2. 解析properties配置文件
            parseProperties();
            // 3. 实例化配置文件对象
            initProperties();
            // 4. 实例化bean 并 维护依赖关系
            initContext();
            // 5. 动态代理实现声明式事务
            proxyBean(configClass);
        } finally {
            // 6. 清空过程数据
            classPathList.clear();
            properties.clear();
        }
    }

    /**
     * 1. 获取包扫描路径下的所有类
     */
    private void getClassPathList() throws Exception {
        // 获取config配置
        if (this.configClass.getAnnotation(ComponentScan.class) != null){
            this.servletContext.log("component scan start");
            // 获取根路径 绝对路径
            String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
            ComponentScan annotation = this.configClass.getAnnotation(ComponentScan.class);
            // 获取包扫描路径
            String[] basePackages = annotation.value();
            if (basePackages.length != 0) {
                for (String basePackage : basePackages) {
                    basePackage = basePackage.trim().replace(BeanFactoryUtil.POINT,BeanFactoryUtil.SLASH);
                    // 获取包路径
                    BeanFactoryUtil.listDirectory(new File(rootPath + basePackage), basePackage, classPathList);
                }
            } else {
                // 包扫描路径不能为空
                throw new RuntimeException("ComponentScan basePackages is not empty");
            }
        } else {
            // 包扫描注解未找到
            throw new RuntimeException("not found ComponentScan");
        }
        this.servletContext.log("component scan end");
    }

    /**
     * 2. 解析properties配置文件
     */
    private void parseProperties() throws Exception {
        // 加载配置文件
        if (this.configClass.getAnnotation(PropertySource.class) != null){
            this.servletContext.log("handle properties start");
            InputStream inputStream = null;
            try {
                String[] propertyPaths = this.configClass.getAnnotation(PropertySource.class).value();
                for (String propertyPath: propertyPaths){
                    this.servletContext.log("read " + propertyPath + " start");
                    inputStream = this.configClass.getClassLoader().getResourceAsStream(propertyPath);
                    if (inputStream != null) {
                        properties.load(inputStream);
                        this.servletContext.log("read " + propertyPath + " finish");
                    } else {
                        // 如果未找到配置文件,则抛出异常
                        throw new RuntimeException("not found " + propertyPath);
                    }
                }
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (Exception ignored) {
                    }
                }
            }
        }
    }

    /**
     * 3. 实例化配置文件对象
     */
    private void initProperties() throws Exception {
        if (this.properties.size() != 0) {
            Object instance = this.configClass.getDeclaredConstructor().newInstance();
            // 获取配置类属性列表
            Field[] fields = this.configClass.getDeclaredFields();
            for (Field field : fields) {
                if (field.getAnnotation(Value.class) != null) {
                    // 获取占位符内的key  并利用反射给属性赋值
                    String key = field.getAnnotation(Value.class).value().replace("${","")
                                                                         .replace("}","");
                    field.setAccessible(true);
                    field.set(instance, this.properties.get(key));
                }
            }
            // 获取配置类方法列表
            Method[] methods = this.configClass.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getAnnotation(Bean.class) != null) {
                    // 获取方法返回值类型 转换为对象id
                    String classId = BeanFactoryUtil.changeLower(method.getReturnType().getSimpleName());
                    method.setAccessible(true);
                    context.put(classId, method.invoke(instance));
                    this.servletContext.log("init "+classId+" finish");
                }
            }
            this.servletContext.log("handle properties end");
        }
    }

    /**
     * 4. 实例化bean 并 维护依赖关系
     */
    private void initContext() throws Exception {
        this.servletContext.log("init beans start");
        // 实例化bean
        for (String classPath : this.classPathList) {
            // 通过反射技术实例化对象
            Class<?> aClass = Class.forName(classPath);
            if (!aClass.isAnnotation() && !aClass.isInterface() && BeanFactoryUtil.checkAnnotation(aClass)) {
                String classId = BeanFactoryUtil.getClassId(aClass);
                if(StringUtils.isNullOrEmpty(classId)) {
                    classId = BeanFactoryUtil.getClassId(aClass.getSimpleName());
                }
                Object instance = aClass.getDeclaredConstructor().newInstance();
                context.put(classId, instance);
                Class<?>[] interfaces = aClass.getInterfaces();
                for (Class<?> inter : interfaces) {
                    context.put(inter.getName(), instance);
                }
                this.servletContext.log("init " + classId + " finish");
            }
        }
        // 维护依赖关系
        for (String classId : context.keySet()) {
            Object parentObject = context.get(classId);
            Field[] fields = parentObject.getClass().getDeclaredFields();
            for (Field field : fields) {
                // 判断是否有@Autowired注解
                if (field.getAnnotation(Autowired.class) != null) {
                    field.setAccessible(true);
                    Object obj = field.get(parentObject);
                    if (obj == null) {
                        // 获取注入对象名称
                        String value = field.getAnnotation(Autowired.class).value();
                        if(StringUtils.isNullOrEmpty(value)) {
                            // 如果注解未设置注入依赖id, 则获取属性名称
                            value = field.getName();
                        }
                        // 根据属性名称获取bean对象
                        Object instance = context.get(value);
                        if (instance != null) {
                            field.setAccessible(true);
                            field.set(parentObject, instance);
                        } else {
                            // 如果根据value未找到bean对象,则抛出异常
                            throw new RuntimeException("not found " + value + " implements " + field.getType().getSimpleName());
                        }
                        this.servletContext.log("autowired " + classId + " " + value + " finish");
                    }

                }
            }
        }
        this.servletContext.log("init beans end");
    }

    /**
     * 5. 动态代理实现声明式事务
     */
    private void proxyBean(Class<?> configClass) throws Exception {
        // 配置代理参数
        if (configClass.getAnnotation(EnableAutoProxy.class) != null){
            this.servletContext.log("proxy beans start");
            // 选择代理方式 默认使用cglib
            boolean proxyType = configClass.getAnnotation(EnableAutoProxy.class).proxyType();
            // 获取代理工厂
            ProxyFactory proxyFactory = (ProxyFactory)context.get("proxyFactory");
            // 配置动态代理
            for (String classPath : this.classPathList) {
                Class<?> aClass = Class.forName(classPath);
                if (!aClass.isAnnotation()) {
                    Method[] methods = aClass.getDeclaredMethods();
                    for (Method method : methods) {
                        if (method.getAnnotation(Transactional.class) != null) {
                            String classId = BeanFactoryUtil.getClassId(aClass);
                            if (proxyType && !aClass.isInterface()) {
                                // jdk动态代理,需要传入实现类
                                Object obj = context.get(classId);
                                // 校验是否已经进行过代理
                                if(BeanFactoryUtil.checkProxy(obj)){
                                    continue;
                                }
                                Object jdkProxy = proxyFactory.getJdkProxy(obj);
                                // 将所有bean对象都替换为代理对象
                                for (String key : context.keySet()) {
                                    if (obj.equals(context.get(key))) {
                                        context.put(key, jdkProxy);
                                    }
                                }
                                this.servletContext.log("JdkProxy bean " + classId + " finish");
                            } else {
                                // 默认cglib
                                Object obj = context.get(classId);
                                // 校验是否已经进行过代理
                                if(BeanFactoryUtil.checkProxy(obj)){
                                    continue;
                                }
                                Object cglibProxy = proxyFactory.getCglibProxy(obj);
                                // 将所有bean对象都替换为代理对象
                                for (String key : context.keySet()) {
                                    if (obj.equals(context.get(key))) {
                                        context.put(key, cglibProxy);
                                    }
                                }
                                this.servletContext.log("CglibProxy bean " + classId + " finish");
                            }
                        }
                    }
                }
            }
            this.servletContext.log("proxy beans end");
        }
    }

    /**
     * 根据beanId获取实例对象
     * @param beanId  实例化对象id
     * @return        实例对象
     */
    public static Object getBean(String beanId) {
        return context.get(beanId);
    }

}
